/*
 * contrib/pgstattuple/pgstatindex.c
 *
 *
 * pgstatindex
 *
 * Copyright (c) 2006 Satoshi Nagayasu <nagayasus@nttdata.co.jp>
 *
 * Permission to use, copy, modify, and distribute this software and
 * its documentation for any purpose, without fee, and without a
 * written agreement is hereby granted, provided that the above
 * copyright notice and this paragraph and the following two
 * paragraphs appear in all copies.
 *
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE TO ANY PARTY FOR DIRECT,
 * INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, INCLUDING
 * LOST PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS
 * DOCUMENTATION, EVEN IF THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED
 * OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * THE AUTHOR SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE.  THE SOFTWARE PROVIDED HEREUNDER IS ON AN "AS
 * IS" BASIS, AND THE AUTHOR HAS NO OBLIGATIONS TO PROVIDE MAINTENANCE,
 * SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
 */

#include "postgres.h"

#include "access/gin_private.h"
#include "access/hash.h"
#include "access/htup_details.h"
#include "access/nbtree.h"
#include "access/relation.h"
#include "access/table.h"
#include "catalog/namespace.h"
#include "catalog/pg_am.h"
#include "funcapi.h"
#include "miscadmin.h"
#include "storage/bufmgr.h"
#include "storage/lmgr.h"
#include "utils/builtins.h"
#include "utils/rel.h"
#include "utils/varlena.h"


/*
 * 由于向后兼容性问题，我们决定为每个函数提供两种类型的接口，分别是 regclass 类型的输入参数和 text 类型的输入参数。
 *
 * 未来版本中将不再支持具有 text 类型输入参数的这些函数。
 */
PG_FUNCTION_INFO_V1(pgstatindex);
PG_FUNCTION_INFO_V1(pgstatindexbyid);
PG_FUNCTION_INFO_V1(pg_relpages);
PG_FUNCTION_INFO_V1(pg_relpagesbyid);
PG_FUNCTION_INFO_V1(pgstatginindex);
PG_FUNCTION_INFO_V1(pgstathashindex);

PG_FUNCTION_INFO_V1(pgstatindex_v1_5);
PG_FUNCTION_INFO_V1(pgstatindexbyid_v1_5);
PG_FUNCTION_INFO_V1(pg_relpages_v1_5);
PG_FUNCTION_INFO_V1(pg_relpagesbyid_v1_5);
PG_FUNCTION_INFO_V1(pgstatginindex_v1_5);

Datum		pgstatginindex_internal(Oid fc_relid, FunctionCallInfo fc_fcinfo);

#define IS_INDEX(r) ((r)->rd_rel->relkind == RELKIND_INDEX)
#define IS_BTREE(r) ((r)->rd_rel->relam == BTREE_AM_OID)
#define IS_GIN(r) ((r)->rd_rel->relam == GIN_AM_OID)
#define IS_HASH(r) ((r)->rd_rel->relam == HASH_AM_OID)

/* ------------------------------------------------
 * 用于 pgstatindex() 的整个 btree 索引统计的结构
 * ------------------------------------------------
 */
typedef struct BTIndexStat
{
	uint32		version;
	uint32		level;
	BlockNumber root_blkno;

	uint64		internal_pages;
	uint64		leaf_pages;
	uint64		empty_pages;
	uint64		deleted_pages;

	uint64		max_avail;
	uint64		free_space;

	uint64		fragments;
} BTIndexStat;

/* ------------------------------------------------
 * 用于 pgstatginindex() 的整个 GIN 索引统计的结构
 * ------------------------------------------------
 */
typedef struct GinIndexStat
{
	int32		version;

	BlockNumber pending_pages;
	int64		pending_tuples;
} GinIndexStat;

/* ------------------------------------------------
 * 用于 pgstathashindex() 的整个 HASH 索引统计的结构
 * ------------------------------------------------
 */
typedef struct HashIndexStat
{
	int32		version;
	int32		space_per_page;

	BlockNumber bucket_pages;
	BlockNumber overflow_pages;
	BlockNumber bitmap_pages;
	BlockNumber unused_pages;

	int64		live_items;
	int64		dead_items;
	uint64		free_space;
} HashIndexStat;

static Datum fc_pgstatindex_impl(Relation fc_rel, FunctionCallInfo fc_fcinfo);
static int64 fc_pg_relpages_impl(Relation fc_rel);
static void fc_GetHashPageStats(Page fc_page, HashIndexStat *fc_stats);

/* ------------------------------------------------------
 * pgstatindex()
 *
 * 用法：SELECT * FROM pgstatindex('t1_pkey');
 *
 * 这里必须保留 superuser() 检查，因为库可能会在扩展未升级的情况下升级，
 * 这意味着在 1.5 版本之前的安装中，这些函数可能会被任何用户调用。
 * ------------------------------------------------------
 */
Datum pgstatindex(PG_FUNCTION_ARGS)
{
	text	   *fc_relname = PG_GETARG_TEXT_PP(0);
	Relation	fc_rel;
	RangeVar   *fc_relrv;

	if (!superuser())
		ereport(ERROR,
				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
				 errmsg("must be superuser to use pgstattuple functions")));

	fc_relrv = makeRangeVarFromNameList(textToQualifiedNameList(fc_relname));
	fc_rel = relation_openrv(fc_relrv, AccessShareLock);

	PG_RETURN_DATUM(fc_pgstatindex_impl(fc_rel, fcinfo));
}

/*
 * 从 pgstattuple 版本 1.5 开始，我们不再需要检查用户是否为超级用户，
 * 因为我们从 PUBLIC 中撤销了对该函数的 EXECUTE 权限。
 * 用户可以根据他们的政策授予访问权限。
 *
 * 其他方面与 pgstatindex（上文）相同。
 */
Datum pgstatindex_v1_5(PG_FUNCTION_ARGS)
{
	text	   *fc_relname = PG_GETARG_TEXT_PP(0);
	Relation	fc_rel;
	RangeVar   *fc_relrv;

	fc_relrv = makeRangeVarFromNameList(textToQualifiedNameList(fc_relname));
	fc_rel = relation_openrv(fc_relrv, AccessShareLock);

	PG_RETURN_DATUM(fc_pgstatindex_impl(fc_rel, fcinfo));
}

/*
 * 这里必须保留 superuser() 检查，因为库可能会在扩展未升级的情况下升级，
 * 这意味着在 1.5 版本之前的安装中，这些函数可能会被任何用户调用。
 */
Datum pgstatindexbyid(PG_FUNCTION_ARGS)
{
	Oid			fc_relid = PG_GETARG_OID(0);
	Relation	fc_rel;

	if (!superuser())
		ereport(ERROR,
				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
				 errmsg("must be superuser to use pgstattuple functions")));

	fc_rel = relation_open(fc_relid, AccessShareLock);

	PG_RETURN_DATUM(fc_pgstatindex_impl(fc_rel, fcinfo));
}

/* 在 v1.5 中不需要进行超级用户检查，见上文 */
Datum pgstatindexbyid_v1_5(PG_FUNCTION_ARGS)
{
	Oid			fc_relid = PG_GETARG_OID(0);
	Relation	fc_rel;

	fc_rel = relation_open(fc_relid, AccessShareLock);

	PG_RETURN_DATUM(fc_pgstatindex_impl(fc_rel, fcinfo));
}

static Datum fc_pgstatindex_impl(Relation fc_rel, FunctionCallInfo fc_fcinfo)
{
	Datum		fc_result;
	BlockNumber fc_nblocks;
	BlockNumber fc_blkno;
	BTIndexStat fc_indexStat;
	BufferAccessStrategy fc_bstrategy = GetAccessStrategy(BAS_BULKREAD);

	if (!IS_INDEX(fc_rel) || !IS_BTREE(fc_rel))
		ereport(ERROR,
				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
				 errmsg("relation \"%s\" is not a btree index",
						RelationGetRelationName(fc_rel))));

	/*
	 * 拒绝尝试读取非本地临时关系；我们可能会获取错误的数据，因为
	 * 我们无法查看拥有会话的本地缓冲区。
	 */
	if (RELATION_IS_OTHER_TEMP(fc_rel))
		ereport(ERROR,
				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
				 errmsg("cannot access temporary tables of other sessions")));

	/*
	 * 一个 !indisready 的索引可能会导致后续的 ERRCODE_DATA_CORRUPTED，
	 * 所以要提前退出。我们能够评估一个 indisready && !indisvalid 的索引，
	 * 但结果可能会令人困惑。例如，该索引的大小对于表的有效索引可能过低。
	 */
	if (!fc_rel->rd_index->indisvalid)
		ereport(ERROR,
				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
				 errmsg("index \"%s\" is not valid",
						RelationGetRelationName(fc_rel))));

	/*
	 * 读取元页
	 */
	{
		Buffer		fc_buffer = ReadBufferExtended(fc_rel, MAIN_FORKNUM, 0, RBM_NORMAL, fc_bstrategy);
		Page		fc_page = BufferGetPage(fc_buffer);
		BTMetaPageData *fc_metad = BTPageGetMeta(fc_page);

		fc_indexStat.version = fc_metad->btm_version;
		fc_indexStat.level = fc_metad->btm_level;
		fc_indexStat.root_blkno = fc_metad->btm_root;

		ReleaseBuffer(fc_buffer);
	}

	/* -- 初始化计数器 -- */
	fc_indexStat.internal_pages = 0;
	fc_indexStat.leaf_pages = 0;
	fc_indexStat.empty_pages = 0;
	fc_indexStat.deleted_pages = 0;

	fc_indexStat.max_avail = 0;
	fc_indexStat.free_space = 0;

	fc_indexStat.fragments = 0;

	/*
	 * 扫描除元页外的所有块
	 */
	fc_nblocks = RelationGetNumberOfBlocks(fc_rel);

	for (fc_blkno = 1; fc_blkno < fc_nblocks; fc_blkno++)
	{
		Buffer		fc_buffer;
		Page		fc_page;
		BTPageOpaque fc_opaque;

		CHECK_FOR_INTERRUPTS();

		/* 读取和锁定缓冲区 */
		fc_buffer = ReadBufferExtended(fc_rel, MAIN_FORKNUM, fc_blkno, RBM_NORMAL, fc_bstrategy);
		LockBuffer(fc_buffer, BUFFER_LOCK_SHARE);

		fc_page = BufferGetPage(fc_buffer);
		fc_opaque = BTPageGetOpaque(fc_page);

		/*
		 * 确定页面类型，并更新总计。
		 *
		 * 请注意，我们任意将已删除页面归为一类，
		 * 而不考虑它们是叶页面还是内部页面。
		 */
		if (P_ISDELETED(fc_opaque))
			fc_indexStat.deleted_pages++;
		else if (P_IGNORE(fc_opaque))
			fc_indexStat.empty_pages++;	/* 这是“半死”状态 */
		else if (P_ISLEAF(fc_opaque))
		{
			int			fc_max_avail;

			fc_max_avail = BLCKSZ - (BLCKSZ - ((PageHeader) fc_page)->pd_special + SizeOfPageHeaderData);
			fc_indexStat.max_avail += fc_max_avail;
			fc_indexStat.free_space += PageGetFreeSpace(fc_page);

			fc_indexStat.leaf_pages++;

			/*
			 * 如果下一个叶子在更早的块上，这意味着存在
			 * 碎片化情况。
			 */
			if (fc_opaque->btpo_next != P_NONE && fc_opaque->btpo_next < fc_blkno)
				fc_indexStat.fragments++;
		}
		else
			fc_indexStat.internal_pages++;

		/* 解锁并释放缓冲区 */
		LockBuffer(fc_buffer, BUFFER_LOCK_UNLOCK);
		ReleaseBuffer(fc_buffer);
	}

	relation_close(fc_rel, AccessShareLock);

	/*----------------------------
	 * 构建结果元组
	 *----------------------------
	 */
	{
		TupleDesc	fc_tupleDesc;
		int			fc_j;
		char	   *fc_values[10];
		HeapTuple	fc_tuple;

		/* 为我们的结果类型构建一个元组描述符 */
		if (get_call_result_type(fc_fcinfo, NULL, &fc_tupleDesc) != TYPEFUNC_COMPOSITE)
			elog(ERROR, "return type must be a row type");

		fc_j = 0;
		fc_values[fc_j++] = psprintf("%d", fc_indexStat.version);
		fc_values[fc_j++] = psprintf("%d", fc_indexStat.level);
		fc_values[fc_j++] = psprintf(INT64_FORMAT,
							   (1 + /* 将元页包含在索引大小中 */
								fc_indexStat.leaf_pages +
								fc_indexStat.internal_pages +
								fc_indexStat.deleted_pages +
								fc_indexStat.empty_pages) * BLCKSZ);
		fc_values[fc_j++] = psprintf("%u", fc_indexStat.root_blkno);
		fc_values[fc_j++] = psprintf(INT64_FORMAT, fc_indexStat.internal_pages);
		fc_values[fc_j++] = psprintf(INT64_FORMAT, fc_indexStat.leaf_pages);
		fc_values[fc_j++] = psprintf(INT64_FORMAT, fc_indexStat.empty_pages);
		fc_values[fc_j++] = psprintf(INT64_FORMAT, fc_indexStat.deleted_pages);
		if (fc_indexStat.max_avail > 0)
			fc_values[fc_j++] = psprintf("%.2f",
								   100.0 - (double) fc_indexStat.free_space / (double) fc_indexStat.max_avail * 100.0);
		else
			fc_values[fc_j++] = pstrdup("NaN");
		if (fc_indexStat.leaf_pages > 0)
			fc_values[fc_j++] = psprintf("%.2f",
								   (double) fc_indexStat.fragments / (double) fc_indexStat.leaf_pages * 100.0);
		else
			fc_values[fc_j++] = pstrdup("NaN");

		fc_tuple = BuildTupleFromCStrings(TupleDescGetAttInMetadata(fc_tupleDesc),
									   fc_values);

		fc_result = HeapTupleGetDatum(fc_tuple);
	}

	return fc_result;
}

/* --------------------------------------------------------
 * pg_relpages()
 *
 * 获取表/索引的页面数量。
 *
 * 用法: SELECT pg_relpages('t1');
 *		  SELECT pg_relpages('t1_pkey');
 *
 * 必须保持 superuser() 检查，见上文。
 * --------------------------------------------------------
 */
Datum pg_relpages(PG_FUNCTION_ARGS)
{
	text	   *fc_relname = PG_GETARG_TEXT_PP(0);
	Relation	fc_rel;
	RangeVar   *fc_relrv;

	if (!superuser())
		ereport(ERROR,
				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
				 errmsg("must be superuser to use pgstattuple functions")));

	fc_relrv = makeRangeVarFromNameList(textToQualifiedNameList(fc_relname));
	fc_rel = relation_openrv(fc_relrv, AccessShareLock);

	PG_RETURN_INT64(fc_pg_relpages_impl(fc_rel));
}

/* 在 v1.5 中不需要进行超级用户检查，见上文 */
Datum pg_relpages_v1_5(PG_FUNCTION_ARGS)
{
	text	   *fc_relname = PG_GETARG_TEXT_PP(0);
	Relation	fc_rel;
	RangeVar   *fc_relrv;

	fc_relrv = makeRangeVarFromNameList(textToQualifiedNameList(fc_relname));
	fc_rel = relation_openrv(fc_relrv, AccessShareLock);

	PG_RETURN_INT64(fc_pg_relpages_impl(fc_rel));
}

/* 必须保持 superuser() 检查，见上文。 */
Datum pg_relpagesbyid(PG_FUNCTION_ARGS)
{
	Oid			fc_relid = PG_GETARG_OID(0);
	Relation	fc_rel;

	if (!superuser())
		ereport(ERROR,
				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
				 errmsg("must be superuser to use pgstattuple functions")));

	fc_rel = relation_open(fc_relid, AccessShareLock);

	PG_RETURN_INT64(fc_pg_relpages_impl(fc_rel));
}

/* 在 v1.5 中不需要进行超级用户检查，见上文 */
Datum pg_relpagesbyid_v1_5(PG_FUNCTION_ARGS)
{
	Oid			fc_relid = PG_GETARG_OID(0);
	Relation	fc_rel;

	fc_rel = relation_open(fc_relid, AccessShareLock);

	PG_RETURN_INT64(fc_pg_relpages_impl(fc_rel));
}

static int64 fc_pg_relpages_impl(Relation fc_rel)
{
	int64		fc_relpages;

	if (!RELKIND_HAS_STORAGE(fc_rel->rd_rel->relkind))
		ereport(ERROR,
				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
				 errmsg("cannot get page count of relation \"%s\"",
						RelationGetRelationName(fc_rel)),
				 errdetail_relkind_not_supported(fc_rel->rd_rel->relkind)));

	/* 注意：这将在非本地临时表上正常工作 */

	fc_relpages = RelationGetNumberOfBlocks(fc_rel);

	relation_close(fc_rel, AccessShareLock);

	return fc_relpages;
}

/* ------------------------------------------------------
 * pgstatginindex()
 *
 * 用法: SELECT * FROM pgstatginindex('ginindex');
 *
 * 必须保持 superuser() 检查，见上文。
 * ------------------------------------------------------
 */
Datum pgstatginindex(PG_FUNCTION_ARGS)
{
	Oid			fc_relid = PG_GETARG_OID(0);

	if (!superuser())
		ereport(ERROR,
				(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
				 errmsg("must be superuser to use pgstattuple functions")));

	PG_RETURN_DATUM(pgstatginindex_internal(fc_relid, fcinfo));
}

/* 在 v1.5 中不需要进行超级用户检查，见上文 */
Datum pgstatginindex_v1_5(PG_FUNCTION_ARGS)
{
	Oid			fc_relid = PG_GETARG_OID(0);

	PG_RETURN_DATUM(pgstatginindex_internal(fc_relid, fcinfo));
}

Datum pgstatginindex_internal(Oid fc_relid, FunctionCallInfo fc_fcinfo)
{
	Relation	fc_rel;
	Buffer		fc_buffer;
	Page		fc_page;
	GinMetaPageData *fc_metadata;
	GinIndexStat fc_stats;
	HeapTuple	fc_tuple;
	TupleDesc	fc_tupleDesc;
	Datum		fc_values[3];
	bool		fc_nulls[3] = {false, false, false};
	Datum		fc_result;

	fc_rel = relation_open(fc_relid, AccessShareLock);

	if (!IS_INDEX(fc_rel) || !IS_GIN(fc_rel))
		ereport(ERROR,
				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
				 errmsg("relation \"%s\" is not a GIN index",
						RelationGetRelationName(fc_rel))));

	/*
	 * 拒绝尝试读取非本地临时关系；我们可能会获取错误的数据，因为
	 * 我们无法查看拥有会话的本地缓冲区。
	 */
	if (RELATION_IS_OTHER_TEMP(fc_rel))
		ereport(ERROR,
				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
				 errmsg("cannot access temporary indexes of other sessions")));

	/* 见 pgstatindex_impl */
	if (!fc_rel->rd_index->indisvalid)
		ereport(ERROR,
				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
				 errmsg("index \"%s\" is not valid",
						RelationGetRelationName(fc_rel))));

	/*
	 * 读取元页
	 */
	fc_buffer = ReadBuffer(fc_rel, GIN_METAPAGE_BLKNO);
	LockBuffer(fc_buffer, GIN_SHARE);
	fc_page = BufferGetPage(fc_buffer);
	fc_metadata = GinPageGetMeta(fc_page);

	fc_stats.version = fc_metadata->ginVersion;
	fc_stats.pending_pages = fc_metadata->nPendingPages;
	fc_stats.pending_tuples = fc_metadata->nPendingHeapTuples;

	UnlockReleaseBuffer(fc_buffer);
	relation_close(fc_rel, AccessShareLock);

	/*
	 * 为我们的结果类型构建一个元组描述符
	 */
	if (get_call_result_type(fc_fcinfo, NULL, &fc_tupleDesc) != TYPEFUNC_COMPOSITE)
		elog(ERROR, "return type must be a row type");

	fc_values[0] = Int32GetDatum(fc_stats.version);
	fc_values[1] = UInt32GetDatum(fc_stats.pending_pages);
	fc_values[2] = Int64GetDatum(fc_stats.pending_tuples);

	/*
	 * 构建并返回元组
	 */
	fc_tuple = heap_form_tuple(fc_tupleDesc, fc_values, fc_nulls);
	fc_result = HeapTupleGetDatum(fc_tuple);

	return fc_result;
}

/* ------------------------------------------------------
 * pgstathashindex()
 *
 * 用法: SELECT * FROM pgstathashindex('hashindex');
 * ------------------------------------------------------
 */
Datum pgstathashindex(PG_FUNCTION_ARGS)
{
	Oid			fc_relid = PG_GETARG_OID(0);
	BlockNumber fc_nblocks;
	BlockNumber fc_blkno;
	Relation	fc_rel;
	HashIndexStat fc_stats;
	BufferAccessStrategy fc_bstrategy;
	HeapTuple	fc_tuple;
	TupleDesc	fc_tupleDesc;
	Datum		fc_values[8];
	bool		fc_nulls[8];
	Buffer		fc_metabuf;
	HashMetaPage fc_metap;
	float8		fc_free_percent;
	uint64		fc_total_space;

	fc_rel = relation_open(fc_relid, AccessShareLock);

	if (!IS_INDEX(fc_rel) || !IS_HASH(fc_rel))
		ereport(ERROR,
				(errcode(ERRCODE_WRONG_OBJECT_TYPE),
				 errmsg("relation \"%s\" is not a hash index",
						RelationGetRelationName(fc_rel))));

	/*
	 * 拒绝尝试读取非本地临时关系；我们可能会获取错误的数据，因为
	 * 我们无法查看拥有会话的本地缓冲区。
	 */
	if (RELATION_IS_OTHER_TEMP(fc_rel))
		ereport(ERROR,
				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
				 errmsg("cannot access temporary indexes of other sessions")));

	/* 见 pgstatindex_impl */
	if (!fc_rel->rd_index->indisvalid)
		ereport(ERROR,
				(errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE),
				 errmsg("index \"%s\" is not valid",
						RelationGetRelationName(fc_rel))));

	/* 从元页面获取我们需要的信息。 */
	memset(&fc_stats, 0, sizeof(fc_stats));
	fc_metabuf = _hash_getbuf(fc_rel, HASH_METAPAGE, HASH_READ, LH_META_PAGE);
	fc_metap = HashPageGetMeta(BufferGetPage(fc_metabuf));
	fc_stats.version = fc_metap->hashm_version;
	fc_stats.space_per_page = fc_metap->hashm_bsize;
	_hash_relbuf(fc_rel, fc_metabuf);

	/* 获取当前关系长度 */
	fc_nblocks = RelationGetNumberOfBlocks(fc_rel);

	/* 为此索引准备访问策略 */
	fc_bstrategy = GetAccessStrategy(BAS_BULKREAD);

	/* 从 blkno 1 开始，因为 0 号块是元页面 */
	for (fc_blkno = 1; fc_blkno < fc_nblocks; fc_blkno++)
	{
		Buffer		fc_buf;
		Page		fc_page;

		CHECK_FOR_INTERRUPTS();

		fc_buf = ReadBufferExtended(fc_rel, MAIN_FORKNUM, fc_blkno, RBM_NORMAL,
								 fc_bstrategy);
		LockBuffer(fc_buf, BUFFER_LOCK_SHARE);
		fc_page = (Page) BufferGetPage(fc_buf);

		if (PageIsNew(fc_page))
			fc_stats.unused_pages++;
		else if (PageGetSpecialSize(fc_page) !=
				 MAXALIGN(sizeof(HashPageOpaqueData)))
			ereport(ERROR,
					(errcode(ERRCODE_INDEX_CORRUPTED),
					 errmsg("index \"%s\" contains corrupted page at block %u",
							RelationGetRelationName(fc_rel),
							BufferGetBlockNumber(fc_buf))));
		else
		{
			HashPageOpaque fc_opaque;
			int			fc_pagetype;

			fc_opaque = HashPageGetOpaque(fc_page);
			fc_pagetype = fc_opaque->hasho_flag & LH_PAGE_TYPE;

			if (fc_pagetype == LH_BUCKET_PAGE)
			{
				fc_stats.bucket_pages++;
				fc_GetHashPageStats(fc_page, &fc_stats);
			}
			else if (fc_pagetype == LH_OVERFLOW_PAGE)
			{
				fc_stats.overflow_pages++;
				fc_GetHashPageStats(fc_page, &fc_stats);
			}
			else if (fc_pagetype == LH_BITMAP_PAGE)
				fc_stats.bitmap_pages++;
			else if (fc_pagetype == LH_UNUSED_PAGE)
				fc_stats.unused_pages++;
			else
				ereport(ERROR,
						(errcode(ERRCODE_INDEX_CORRUPTED),
						 errmsg("unexpected page type 0x%04X in HASH index \"%s\" block %u",
								fc_opaque->hasho_flag, RelationGetRelationName(fc_rel),
								BufferGetBlockNumber(fc_buf))));
		}
		UnlockReleaseBuffer(fc_buf);
	}

	/* 完成对索引的访问 */
	index_close(fc_rel, AccessShareLock);

	/* 将未使用的页面计为自由空间。 */
	fc_stats.free_space += (uint64) fc_stats.unused_pages * fc_stats.space_per_page;

	/*
	 * 可用于元组的总空间不包括元页面和位图页面。
	 */
	fc_total_space = (uint64) (fc_nblocks - (fc_stats.bitmap_pages + 1)) *
		fc_stats.space_per_page;

	if (fc_total_space == 0)
		fc_free_percent = 0.0;
	else
		fc_free_percent = 100.0 * fc_stats.free_space / fc_total_space;

	/*
	 * 为我们的结果类型构建一个元组描述符
	 */
	if (get_call_result_type(fcinfo, NULL, &fc_tupleDesc) != TYPEFUNC_COMPOSITE)
		elog(ERROR, "return type must be a row type");

	fc_tupleDesc = BlessTupleDesc(fc_tupleDesc);

	/*
	 * 构建并返回元组
	 */
	MemSet(fc_nulls, 0, sizeof(fc_nulls));
	fc_values[0] = Int32GetDatum(fc_stats.version);
	fc_values[1] = Int64GetDatum((int64) fc_stats.bucket_pages);
	fc_values[2] = Int64GetDatum((int64) fc_stats.overflow_pages);
	fc_values[3] = Int64GetDatum((int64) fc_stats.bitmap_pages);
	fc_values[4] = Int64GetDatum((int64) fc_stats.unused_pages);
	fc_values[5] = Int64GetDatum(fc_stats.live_items);
	fc_values[6] = Int64GetDatum(fc_stats.dead_items);
	fc_values[7] = Float8GetDatum(fc_free_percent);
	fc_tuple = heap_form_tuple(fc_tupleDesc, fc_values, fc_nulls);

	PG_RETURN_DATUM(HeapTupleGetDatum(fc_tuple));
}

/* -------------------------------------------------
 * GetHashPageStats()
 *
 * 收集单个哈希页面的统计信息
 * -------------------------------------------------
 */
static void fc_GetHashPageStats(Page fc_page, HashIndexStat *fc_stats)
{
	OffsetNumber fc_maxoff = PageGetMaxOffsetNumber(fc_page);
	int			fc_off;

	/* 计算活动和死亡元组以及自由空间 */
	for (fc_off = FirstOffsetNumber; fc_off <= fc_maxoff; fc_off++)
	{
		ItemId		fc_id = PageGetItemId(fc_page, fc_off);

		if (!ItemIdIsDead(fc_id))
			fc_stats->live_items++;
		else
			fc_stats->dead_items++;
	}
	fc_stats->free_space += PageGetExactFreeSpace(fc_page);
}
